package org.thanos.simulator.util;

import cn.hutool.core.lang.Validator;
import cn.hutool.core.text.CharSequenceUtil;

import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;

public class NetworkUtil {
    private static final Integer DEFAULT_NETWORK_PREFIX_LENGTH = 24;

    private NetworkUtil() {

    }

    public static List<String> getIps() throws SocketException {
        List<String> ips = new ArrayList<>();
        Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
        while (networkInterfaces.hasMoreElements()) {
            NetworkInterface networkInterface = networkInterfaces.nextElement();
            Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
            while (inetAddresses.hasMoreElements()) {
                InetAddress inetAddress = inetAddresses.nextElement();
                if (!inetAddress.isLoopbackAddress() && !inetAddress.isLinkLocalAddress()) {
                    ips.add(inetAddress.getHostAddress());
                }
            }
        }
        return ips;
    }

    public static List<NetworkInterfaceDTO> getNetworkInterfaces() {
        List<NetworkInterfaceDTO> list = new ArrayList<>();
        try {
            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
            while (networkInterfaces.hasMoreElements()) {
                NetworkInterface networkInterface = networkInterfaces.nextElement();
                boolean isup = networkInterface.isUp();
                if (isup) {
                    List<InterfaceAddress> interfaceAddresses = networkInterface.getInterfaceAddresses();
                    for (InterfaceAddress interfaceAddress : interfaceAddresses) {
                        InetAddress inetAddress = interfaceAddress.getAddress();
                        if (!inetAddress.isLoopbackAddress() && !inetAddress.isLinkLocalAddress() && Validator.isIpv4(inetAddress.getHostAddress())) {
                            NetworkInterfaceDTO ni = new NetworkInterfaceDTO(buildNetworkInterfaceName(networkInterface),
                                    inetAddress.getHostAddress(),
                                    Objects.isNull(interfaceAddress.getBroadcast())
                                            ? CharSequenceUtil.EMPTY : interfaceAddress.getBroadcast().getHostAddress(),
                                    NetworkUtil.getSubnetMask((int) interfaceAddress.getNetworkPrefixLength()),
                                    interfaceAddress.getNetworkPrefixLength());
                            list.add(ni);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    private static String buildNetworkInterfaceName(NetworkInterface networkInterface) {
        // eth7 (Dell Giga Ethernet)
        return CharSequenceUtil.concat(true, networkInterface.getName(),
                CharSequenceUtil.SPACE, "(", networkInterface.getDisplayName(), ")");
    }

    /**
     * 返回子网掩码
     *
     * @param networkPrefixLength
     * @return
     */
    public static String getSubnetMask(Integer networkPrefixLength) {
        int netmaskNumeric = getNetmaskNumeric(networkPrefixLength);
        return convertNumericIpToSymbolic(netmaskNumeric);
    }

    public static String getBroadcastAddress(String ipAddress, Integer networkPrefixLength) {
        int netmaskNumeric = getNetmaskNumeric(networkPrefixLength);
        int baseIPNumeric = getBaseIPNumeric(ipAddress);
        if (netmaskNumeric == 0xffffffff) {
            return "0.0.0.0";
        }
        int numberOfBits;
        for (numberOfBits = 0; numberOfBits < 32; numberOfBits++) {
            if ((netmaskNumeric << numberOfBits) == 0) break;
        }
        Integer numberOfIPs = 0;
        for (int n = 0; n < (32 - numberOfBits); n++) {
            numberOfIPs = numberOfIPs << 1;
            numberOfIPs = numberOfIPs | 0x01;
        }

        Integer baseIP = baseIPNumeric & netmaskNumeric;
        Integer ourIP = baseIP + numberOfIPs;

        return convertNumericIpToSymbolic(ourIP);
    }

    /**
     * 返回给定范围内可用主机的数量
     */
    public static Long getNumberOfHosts(Integer networkPrefixLength) {
        int netmaskNumeric = getNetmaskNumeric(networkPrefixLength);
        int numberOfBits;
        for (numberOfBits = 0; numberOfBits < 32; numberOfBits++) {
            if ((netmaskNumeric << numberOfBits) == 0) {
                break;
            }
        }
        Double x = Math.pow(2, (32 - numberOfBits));
        if (x.intValue() == -1) {
            x = 1D;
        }
        return x.longValue();
    }

    /**
     * 主机范围
     *
     * @param ipAddress
     * @param networkPrefixLength
     * @return
     */
    public static String getHostAddressRange(String ipAddress, Integer networkPrefixLength) {
        int netmaskNumeric = getNetmaskNumeric(networkPrefixLength);
        int baseIPNumeric = getBaseIPNumeric(ipAddress);
        int numberOfBits;
        for (numberOfBits = 0; numberOfBits < 32; numberOfBits++) {
            if ((netmaskNumeric << numberOfBits) == 0) break;
        }
        int numberOfIPs = 0;
        for (int n = 0; n < (32 - numberOfBits); n++) {
            numberOfIPs = numberOfIPs << 1;
            numberOfIPs = numberOfIPs | 0x01;
        }

        int baseIP = baseIPNumeric & netmaskNumeric;
        String firstIP = convertNumericIpToSymbolic(baseIP + 1);
        String lastIP = convertNumericIpToSymbolic(baseIP + numberOfIPs - 1);
        return firstIP + " - " + lastIP;
    }

    /**
     * 检查给定的 IP 地址是否包含在子网中
     *
     * @return
     */
    public static boolean checkIpInSubnet(String checkIpAddress, String ipAddress, Integer networkPrefixLength) {
        int netmaskNumeric = getNetmaskNumeric(networkPrefixLength);
        int baseIPNumeric = getBaseIPNumeric(ipAddress);
        int checkingIP = 0;
        String[] st = checkIpAddress.split("\\.");
        if (st.length != 4) {
            throw new NumberFormatException("Invalid IP address: " + checkIpAddress);
        }
        int i = 24;
        for (String s : st) {
            int value = Integer.parseInt(s);
            if (value != (value & 0xff)) {
                throw new NumberFormatException("Invalid IP address: " + checkIpAddress);
            }
            checkingIP += value << i;
            i -= 8;
        }
        return (baseIPNumeric & netmaskNumeric) == (checkingIP & netmaskNumeric);
    }


    private static Integer getNetmaskNumeric(Integer networkPrefixLength) {
        int netmaskNumeric = 0xffffffff;
        return netmaskNumeric << (32 - networkPrefixLength);
    }

    public static Integer getBaseIPNumeric(String ipAddress) {
        int i = 24;
        int baseIPNumeric = 0;
        String[] st = ipAddress.split("\\.");
        for (String s : st) {
            int value = Integer.parseInt(s);
            baseIPNumeric += value << i;
            i -= 8;
        }
        return baseIPNumeric;
    }

    private static String convertNumericIpToSymbolic(Integer numericIp) {
        StringBuilder sb = new StringBuilder(15);
        for (int shift = 24; shift > 0; shift -= 8) {
            // process 3 bytes, from high order byte down.
            sb.append((numericIp >>> shift) & 0xff);
            sb.append('.');
        }
        sb.append(numericIp & 0xff);
        return sb.toString();
    }

    public static int calNetworkPrefixLength(String subnetMask) {
        String[] st = subnetMask.split("\\.");
        StringBuilder stringBuilder;
        String str;
        int count;
        int prefixLength = 0;
        for (String s : st) {
            stringBuilder = toBin(Integer.parseInt(s));
            str = stringBuilder.reverse().toString();
            count = 0;
            for (int i = 0; i < str.length(); i++) {
                i = str.indexOf('1', i);
                if (i == -1) {
                    break;
                }
                count++;
            }
            prefixLength += count;
        }
        return prefixLength;
    }

    private static StringBuilder toBin(int x) {
        StringBuilder sb = new StringBuilder();
        sb.append(x % 2);
        x /= 2;
        while (x > 0) {
            sb.append(x % 2);
            x /= 2;
        }
        return sb;
    }

    public static class NetworkInterfaceDTO {
        private String networkName;
        private String ipAddress;
        private String broadcast;
        private String subnetMask;
        private Short networkPrefixLength;

        public NetworkInterfaceDTO(String networkName, String ipAddress, String broadcast, String subnetMask, Short networkPrefixLength) {
            this.networkName = networkName;
            this.ipAddress = ipAddress;
            this.broadcast = broadcast;
            this.subnetMask = subnetMask;
            this.networkPrefixLength = networkPrefixLength;
        }

        public String getNetworkName() {
            return networkName;
        }

        public void setNetworkName(String networkName) {
            this.networkName = networkName;
        }

        public String getIpAddress() {
            return ipAddress;
        }

        public void setIpAddress(String ipAddress) {
            this.ipAddress = ipAddress;
        }

        public String getBroadcast() {
            return broadcast;
        }

        public void setBroadcast(String broadcast) {
            this.broadcast = broadcast;
        }

        public String getSubnetMask() {
            return subnetMask;
        }

        public void setSubnetMask(String subnetMask) {
            this.subnetMask = subnetMask;
        }

        public Short getNetworkPrefixLength() {
            return networkPrefixLength;
        }

        public void setNetworkPrefixLength(Short networkPrefixLength) {
            this.networkPrefixLength = networkPrefixLength;
        }
    }
}
